/*
 * Copyright 2012 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.dspl.validate;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

import com.google.common.base.Objects;
import com.google.dspl.model.Attribute;
import com.google.dspl.model.Concept;
import com.google.dspl.model.Concept.ConceptProperty;
import com.google.dspl.model.Dataset;
import com.google.dspl.model.Slice;
import com.google.dspl.model.Table;
import com.google.dspl.model.Table.Column;
import com.google.dspl.model.Topic;

import java.io.Serializable;

/**
 * General purpose mechanism to refer to a single item in a {@link Dataset}.
 *
 * @author James Grimm
 */
public final class ItemReference implements Serializable {

  private static final long serialVersionUID = 7993684378407427370L;

  // Fields are non-final for GWT serialization
  private String datasetId;
  private String attributeId;
  private String tableId;
  private String columnId;
  private String conceptId;
  private String propertyId;
  private String sliceId;
  private String topicId;
  private Integer row;

  // Computed field
  private String path;

  // For GWT serialization
  private ItemReference() {
  }

  private ItemReference(Dataset dataset,
      Attribute attribute,
      Table table,
      Column column,
      Concept concept,
      ConceptProperty property,
      Slice slice,
      Topic topic,
      Integer row) {
    this.datasetId = checkNotNull(dataset).getDatasetId();
    this.attributeId = attribute != null ? attribute.getId() : null;
    this.tableId = table != null ? table.getId() : null;
    this.columnId = column != null ? column.getId() : null;
    this.conceptId = concept != null ? concept.getId() : null;
    this.propertyId = property != null ? property.getId() : null;
    this.sliceId = slice != null ? slice.getId() : null;
    this.topicId = topic != null ? topic.getId() : null;

    this.row = row;
    this.path =
        buildPath(datasetId, attributeId, tableId, columnId, conceptId, propertyId, sliceId, topicId, row);
  }

  public String getDatasetId() {
    return datasetId;
  }

  public String getTableId() {
    return tableId;
  }

  public String getColumnId() {
    return columnId;
  }

  public String getConceptId() {
    return conceptId;
  }

  public String getPropertyId() {
    return propertyId;
  }

  public String getAttributeId() {
    return attributeId;
  }

  public String getSliceId() {
    return sliceId;
  }

  public String getTopicId() {
    return topicId;
  }

  public Integer getDataRow() {
    return row;
  }

  /**
   * View of this reference as a path that specifies an item in a Dataset in a single String.
   */
  public String asPath() {
    return path;
  }

  private static String buildPath(String datasetId, String attributeId,
      String tableId, String columnId, String conceptId, String propertyId,
      String sliceId, String topicId, Integer row) {

    StringBuilder path = new StringBuilder();
    path.append("/dataset/")
        .append(datasetId);
    if (conceptId != null) {
      path.append("/concept/").append(conceptId);
      if (propertyId != null) {
        path.append("/property/").append(propertyId);
      } else if (attributeId != null) {
        path.append("/attribute/").append(attributeId);
      }
    } else if (tableId != null) {
      path.append("/table/").append(tableId);
      if (columnId != null) {
        path.append("/column/").append(columnId);
        if (row != null) {
          path.append("/row/").append(row);
        }
      }
    } else if (sliceId != null) {
      path.append("/slice/").append(sliceId);
    } else if (topicId != null) {
      path.append("/topic/").append(topicId);
    } else if (attributeId != null) {
      path.append("/attribute/").append(attributeId);
    }

    return path.toString();
  }

  @Override
  public boolean equals(Object obj) {
    if (obj == this) {
      return true;
    }

    if (!(obj instanceof ItemReference)) {
      return false;
    }

    ItemReference rhs = (ItemReference) obj;
    return Objects.equal(path, rhs.path);
  }

  @Override
  public int hashCode() {
    return path.hashCode();
  }

  @Override
  public String toString() {
    return path;
  }

  public static ItemReference reference(Dataset dataset) {
    checkNotNull(dataset);
    return new ItemReference(dataset, null, null, null, null, null, null, null, null);
  }

  public static ItemReference reference(Dataset dataset, Table table) {
    checkNotNull(dataset);
    checkNotNull(table);
    return new ItemReference(dataset, null, table, null, null, null, null, null, null);
  }

  public static ItemReference reference(Dataset dataset, Table table, Column column) {
    checkNotNull(dataset);
    checkNotNull(table);
    checkNotNull(column);
    return new ItemReference(dataset, null, table, column, null, null, null, null, null);
  }

  public static ItemReference reference(Dataset dataset, Table table, Column column, int dataRow) {
    checkNotNull(dataset);
    checkNotNull(table);
    checkNotNull(column);
    checkArgument(dataRow >= 0);
    return new ItemReference(dataset, null, table, column, null, null, null, null, dataRow);
  }

  public static ItemReference reference(Dataset dataset, Concept concept) {
    checkNotNull(dataset);
    checkNotNull(concept);
    return new ItemReference(dataset, null, null, null, concept, null, null, null, null);
  }

  public static ItemReference reference(Dataset dataset, Concept concept,
      ConceptProperty property) {
    checkNotNull(dataset);
    checkNotNull(concept);
    checkNotNull(property);
    return new ItemReference(dataset, null, null, null, concept, property, null, null, null);
  }

  public static ItemReference reference(Dataset dataset, Concept concept,
      Attribute attribute) {
    checkNotNull(dataset);
    checkNotNull(concept);
    checkNotNull(attribute);
    return new ItemReference(dataset, attribute, null, null, concept, null, null, null, null);
  }

  public static ItemReference reference(Dataset dataset, Slice slice) {
    checkNotNull(dataset);
    checkNotNull(slice);
    return new ItemReference(dataset, null, null, null, null, null, slice, null, null);
  }

  public static ItemReference reference(Dataset dataset, Topic topic) {
    checkNotNull(dataset);
    checkNotNull(topic);
    return new ItemReference(dataset, null, null, null, null, null, null, topic, null);
  }

  public static ItemReference reference(Dataset dataset, Attribute attribute) {
    checkNotNull(dataset);
    checkNotNull(attribute);
    return new ItemReference(dataset, attribute, null, null, null, null, null, null, null);
  }
}
